/**
 * 
 */
package br.org.bertol.mestrado.engine.fitness;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Random;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.optimisation.entity.AbstractUnit;
import br.org.bertol.mestrado.engine.optimisation.moo.AbstractParetoDominace;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoMinMaxDominace;

/**
 * @author contaqualquer
 */
public class MutantAnalysis extends AbstractCombinatorialFitness<String> {

    /**
     * Fronteira de Pareto.
     */
    private transient ParetoMinMaxDominace paretoDominace    =
                                                                     new ParetoMinMaxDominace();

    /**
     * Teste case selecionados.
     */
    private transient List<String>         testCase          =
                                                                     new ArrayList<String>();

    /***/
    private final transient double         changeFactorOperator;

    /***/
    private transient float                chanceIndicator;

    /***/
    private final transient float          CHANCE_PORCENTAGE = 0.5f;

    /**
     * @param classes
     *            Lista com todas as classes disponíveis
     * @param changePercentual
     *            Percentual de troca de operadores
     */
    public MutantAnalysis(final ArrayList<String> classes,
        final double changePercentual) {
        super(classes);

        changeFactorOperator = changePercentual;

        chanceIndicator = 0f;

        fitnessCount = 0L;

    }

    @Override
    public final boolean fitness(final List<String> route,
            final EnumMap<Objective, Number> height) {

        for (final Objective objective : height.keySet()) {

            switch (objective) {

                case MUTANTES:
                    height.put(objective, objective.getConstraintHeight(route));

                    testCase = objective.getInfo();

                    break;
                case COBERTURA:
                    height.put(objective, objective
                            .getConstraintHeight(testCase));

                    testCase = objective.getInfo();

                    break;
                default:
                    break;
            }

            fitnessCount += 1;
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public final Object clone() throws CloneNotSupportedException {

        final MutantAnalysis tmp = (MutantAnalysis) super.clone();

        tmp.testCase = (List<String>) ((ArrayList<String>) this.testCase)
                .clone();

        tmp.fitnessCount = this.fitnessCount;

        return tmp;
    }

    @Override
    public final void reset() {
        testCase.clear();

        fitnessCount = 0L;
    }

    @Override
    public final boolean stopCriteria() {
        return false;
    }

    @SuppressWarnings("unchecked")
    @Override
    public final <U extends AbstractUnit<String>> U doBetter(final U unit,
            final Object... varags)
            throws CloneNotSupportedException {

        final U changeUnit = (U) unit.clone();

        final ArrayList<String> reducedClasses =
                (ArrayList<String>) classes.clone();

        reducedClasses.removeAll(changeUnit.getRoute());

        final int totalChanges =
                (int) (unit.getRoute().size() * changeFactorOperator);

        for (int counter = 0; counter < totalChanges; counter++) {
            final double roulet = Math.random();
            if (roulet <= CHANCE_PORCENTAGE) {

                TipoMovimento.SUBSTITUIR
                        .execute(changeUnit, reducedClasses);
            } else {
                TipoMovimento.ADICIONAR.execute(changeUnit, reducedClasses);

            }
        }

        changeUnit.calcFitness();

        U reduceUnit = (U) changeUnit.clone();

        final EnumMap<Objective, Number> calc = reduceUnit.getHeight().clone();

        final List<String> tmpL = (List<String>) reduceUnit.getRoute().clone();

        // caso tenha obtido 100% de cobertura, tenta reduzir mutantes
        for (final String operador : tmpL) {
            reduceUnit.removeClass(operador);

            fitness(reduceUnit.getRoute(), reduceUnit.getHeight());

            if (paretoDominace.compare(reduceUnit.getHeight(), calc) != AbstractParetoDominace.LEFT_DOMINE) {
                reduceUnit.addClass(operador);
                fitness(reduceUnit.getRoute(), reduceUnit.getHeight());
            }
        }
        if (new Random().nextBoolean()
                && Objective.COBERTURA
                .formatValue(calc.get(Objective.COBERTURA))
                .intValue() < 1) {
            try {
                reduceUnit = hillClimbing(reduceUnit);
            } catch (Exception e) {
                Logger.getLogger(getClass()).error("Erro ao melhorar solução.",
                                                   e);
            }
        }

        reduceUnit.calcFitness();

        return reduceUnit;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        final StringBuilder buffer = new StringBuilder();

        buffer.append(testCase.toString());

        return buffer.toString();
    }

    @SuppressWarnings("unchecked")
    private <U extends AbstractUnit<String>> U hillClimbing(final U startPath)
            throws Exception {

        U resposta = (U) startPath.clone();

        final int numIteracoes = 20;

        for (int numEvaluations = 0; numEvaluations < numIteracoes; numEvaluations++) {
            // come�a novamenteciclo de pesquisa

            U bestpath = (U) resposta.clone();

            boolean moveMade = false;

            for (int pointer = 0; pointer < bestpath.getRoute().size()
                    && numEvaluations < numIteracoes; pointer++, numEvaluations++) {

                final U alternativePath = (U) bestpath.clone();

                alternativePath.removeClass(pointer);

                alternativePath.calcFitness();

                // atualiza melhor caminho se houver
                if (alternativePath.getHeight().get(Objective.MUTANTES)
                        .intValue() < resposta.getHeight()
                        .get(Objective.MUTANTES).intValue()) {

                    resposta = (U) alternativePath.clone();

                    moveMade = true;

                    // caso tamanho seja 1, para tudo
                    if (resposta.getRoute().size() == 1) {
                        moveMade = false;
                        break;
                    }

                }
            }

            // critério de parada
            // caso não tenha sido feita nenhuma avaliação
            if (!moveMade) {
                break;
            }
        }
        return resposta;

    }

    /**
     * Tipos de movimentos para o problema de mutação.
     * @author contaqualquer
     */
    private enum TipoMovimento {

        /***/
        ADICIONAR {
            /**
             * {@inheritDoc}
             */
            protected void execute(final AbstractUnit<String> unit,
                    final ArrayList<String> classes) {

                if (!classes.isEmpty()) {

                    final int pointer = (int) Math
                            .floor((classes.size() * Math
                            .random()));

                    unit.addClass(classes.remove(pointer));
                }
            }

        },
                /***/
        SUBSTITUIR {
            /**
             * {@inheritDoc}
             */
            protected void execute(final AbstractUnit<String> unit,
                    final ArrayList<String> classes) {

                if (!classes.isEmpty()) {
                    final int pointer1 = (int) (unit.getRoute().size() * Math
                            .random());

                    final int pointer2 = (int) (classes.size() * Math
                            .random());

                    unit.removeClass(pointer1);

                    unit.addClass(classes.remove(pointer2));
                }
            }
        },
                /***/
        REMOVER {
            /**
             * {@inheritDoc}
             */
            protected void execute(final AbstractUnit<String> unit,
                    final ArrayList<String> classes) {

            }
        };

        protected abstract void execute(final AbstractUnit<String> unit,
                final ArrayList<String> classes);
    }
}
